/**
 * @author		Sergey Suzdalskiy
 * @e-mail:		sergey.suzdalskiy@gmail.com
 */


package ua.kiev.gcore.protobuf {	
	import flash.errors.*;
	import flash.utils.*;
	/**
	 * @private
	 */
	public final class ReadUtils {
		
		private static const FieldGuidLow:int = 1;
		private static const FieldGuidHigh:int = 2;
		
		public static function skip(input:IDataInput, wireType:uint):void {
			switch (wireType) {
			case WireType.VARINT:
				while (input.readUnsignedByte() > 0x80) {}
				break
			case WireType.FIXED_64_BIT:
				input.readInt()
				input.readInt()
				break
			case WireType.LENGTH_DELIMITED:
				for (var i:uint = readTYPE_UINT32(input); i != 0; i--) {
					input.readByte()
				}
				break
			case WireType.FIXED_32_BIT:
				input.readInt()
				break
			default:
				throw new IOError("Invalid wire type: " + wireType)
			}
		}
		public static function readTYPE_DOUBLE(input:IDataInput):Number {
			return input.readDouble()
		}
		public static function readTYPE_FLOAT(input:IDataInput):Number {
			return input.readFloat()
		}
		public static function readTYPE_INT64(input:IDataInput):Int64 {
			const result:Int64 = new Int64
			var b:uint
			var i:uint = 0
			for (;; i += 7) {
				b = input.readUnsignedByte()
				if (i == 28) {
					break
				} else {
					if (b >= 0x80) {
						result.low |= ((b & 0x7f) << i)
					} else {
						result.low |= (b << i)
						return result
					}
				}
			}
			if (b >= 0x80) {
				b &= 0x7f
				result.low |= (b << i)
				result.high = b >>> 4
			} else {
				result.low |= (b << i)
				result.high = b >>> 4
				return result
			}
			for (i = 3;; i += 7) {
				b = input.readUnsignedByte()
				if (i < 32) {
					if (b >= 0x80) {
						result.high |= ((b & 0x7f) << i)
					} else {
						result.high |= (b << i)
						break
					}
				}
			}
			return result
		}
		public static function readTYPE_UINT64(input:IDataInput):UInt64 {
			const result:UInt64 = new UInt64
			var b:uint
			var i:uint = 0
			for (;; i += 7) {
				b = input.readUnsignedByte()
				if (i == 28) {
					break
				} else {
					if (b >= 0x80) {
						result.low |= ((b & 0x7f) << i)
					} else {
						result.low |= (b << i)
						return result
					}
				}
			}
			if (b >= 0x80) {
				b &= 0x7f
				result.low |= (b << i)
				result.high = b >>> 4
			} else {
				result.low |= (b << i)
				result.high = b >>> 4
				return result
			}
			for (i = 3;; i += 7) {
				b = input.readUnsignedByte()
				if (i < 32) {
					if (b >= 0x80) {
						result.high |= ((b & 0x7f) << i)
					} else {
						result.high |= (b << i)
						break
					}
				}
			}
			return result
		}
		public static function readTYPE_INT32(input:IDataInput):int {
			return int(readTYPE_UINT32(input))
		}
		public static function readTYPE_FIXED64(input:IDataInput):UInt64 {
			const result:UInt64 = new UInt64
			result.low = input.readUnsignedInt()
			result.high = input.readUnsignedInt()
			return result
		}
		public static function readTYPE_FIXED32(input:IDataInput):uint {
			return input.readUnsignedInt()
		}
		public static function readTYPE_BOOL(input:IDataInput):Boolean {
			return readTYPE_UINT32(input) != 0
		}
		public static function readTYPE_STRING(input:IDataInput):String {
			const length:uint = readTYPE_UINT32(input)
//			const length:uint = input.readShort();
			return input.readUTFBytes(length)
		}
		public static function readTYPE_BYTES(input:IDataInput):ByteArray {
			const result:ByteArray = new ByteArray
			const length:uint = readTYPE_UINT32(input)
			if (length > 0) {
				input.readBytes(result, 0, length)
			}
			return result
		}
		public static function readTYPE_UINT32(input:IDataInput):uint {
			var result:uint = 0
			for (var i:uint = 0;; i += 7) {
				const b:uint = input.readUnsignedByte()
				if (i < 32) {
					if (b >= 0x80) {
						result |= ((b & 0x7f) << i)
					} else {
						result |= (b << i)
						break
					}
				} else {
					while (input.readUnsignedByte() >= 0x80) {}
					break
				}
			}
			return result
		}
		public static function readTYPE_ENUM(input:IDataInput):int {
			return readTYPE_INT32(input)
		}
		public static function readTYPE_SFIXED32(input:IDataInput):int {
			return input.readInt()
		}
		public static function readTYPE_SFIXED64(input:IDataInput):Int64 {
			const result:Int64 = new Int64
			result.low = input.readUnsignedInt()
			result.high = input.readInt()
			return result
		}
		public static function readTYPE_SINT32(input:IDataInput):int {
			return ZigZag.decode32(readTYPE_UINT32(input))
		}
		public static function readTYPE_SINT64(input:IDataInput):Int64 {
			const result:Int64 = readTYPE_INT64(input)
			const low:uint = result.low
			const high:uint = result.high
			result.low = ZigZag.decode64low(low, high)
			result.high = ZigZag.decode64high(low, high)
			return result
		}
			
			
		public static function readTYPE_MESSAGE(input:IDataInput):WritingBuffer {
			var result:WritingBuffer = new WritingBuffer();
			
			const length:uint = readTYPE_UINT32(input)
			if (input.bytesAvailable < length) {
				throw new IOError("Invalid message length: " + length)
			}
			const bytesAfterSlice:uint = input.bytesAvailable - length;
			
			input.readBytes(result, 0, length);			
			
			if (input.bytesAvailable != bytesAfterSlice) {
				throw new IOError("Invalid nested message")
			}
			return result;
		}
		public static function readPackedRepeated(input:IDataInput,
				readFuntion:Function, value:Array):void {
			const length:uint = readTYPE_UINT32(input)
			if (input.bytesAvailable < length) {
				throw new IOError("Invalid message length: " + length)
			}
			const bytesAfterSlice:uint = input.bytesAvailable - length
			while (input.bytesAvailable > bytesAfterSlice) {
				value.push(readFuntion(input))
			}
			if (input.bytesAvailable != bytesAfterSlice) {
				throw new IOError("Invalid packed repeated data")
			}
		}
		
		public static function readUnknown(input:IDataInput, tag:uint):Object {
			var value:Object;
			switch (tag & 7) {
				case WireType.VARINT:
					value = ReadUtils.readTYPE_UINT64(input)
					break
				case WireType.FIXED_64_BIT:
					value = ReadUtils.readTYPE_FIXED64(input)
					break
				case WireType.LENGTH_DELIMITED:
					value = ReadUtils.readTYPE_BYTES(input)
					break
				case WireType.FIXED_32_BIT:
					value = ReadUtils.readTYPE_FIXED32(input)
					break
				default:
					throw new IOError("readUnknown() Invalid wire type: " + (tag & 7))
			}
			return value;
		}
	}
}
